package com.football.gkjo.server;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

/**
 * 
 * 클라이언트의 입력을 담당할 쓰레드다.
 *
 */
public class GKJoServerThread extends Thread {

	// 메뉴 번호에 대한 값 범위이다
	private static final int MENU_MIN = 0;
	private static final int MENU_MAX = 4;

	public static final int LIMIT_SERVER = 3;
	public static final int LIMIT_MATCH = 2;

	// 클라이언트의 소켓 객체다.
	private Socket socket;

	// 클라이언트와 통신해야할 BufferedReader (문자열을 주고 받을 계획이기때문에)
	private BufferedReader reader;
	// 클라이언트와 통신해야할 BufferedWriter (문자열을 주고 받을 계획이기때문에)
	private BufferedWriter writer;

	/*---------------멤버변수-------------------*/
	// 클라이언트 ID
	private String userId;

	// 생성자(소켓을 초기화 시켜준다)
	public GKJoServerThread(Socket socket) {
		this.socket = socket;
	}

	public String getUserId() {
		return this.userId;
	}

	public void sendMessage(String message) throws IOException {
		/*
		 * 꼭 명심해야할 것은 소켓 통신의 핵심은 Reader와 Writer이다. 클라이언트에게 전송하고 싶다면 writer를 이용해서
		 * 전송하고 클라이언트가 보낸 메시지를 알고 싶다면 reader를 통해서 가져온다는 것만 명심하자.
		 */
		writer.write(message + "\n");
		writer.flush();
	}

	public boolean searchUserId(String userId) {
		/*
		 * GKJoServer에 CLIENTS라는 Static ArrayList가 있다. 이 CLIENTS 멤버변수는 접속한
		 * 클라이언트의 쓰레드(GKJoServerThread)를 갖고 있다. Static으로서 공통적으로 접근 가능하기때문에
		 * synchronized를 해주면 값에 대한 신뢰성을 높일 수 있다.
		 */

		/*
		 * 접속한 클라이언트의 아이디가 존재하는지 검사하자.
		 */
		synchronized (GKJoServer.CLIENTS) {
			for (int i = 0; i < GKJoServer.CLIENTS.size(); i++) {
				if (userId.equals(GKJoServer.CLIENTS.get(i).getUserId())) {
					return true;
				}
			}
			return false;
		}
	}

	public String readMessage() throws IOException {
		/*
		 * 메서드의 장점은 똑같은 작업들을 할 때 재사용할 수 있다는 것이다. 앞으로 클라이언트의 메시지를 읽는 경우가 많을 것이니
		 * 작성해놓으면 편리하다.
		 */
		return reader.readLine();
	}

	public void welcomeMsg() throws IOException {
		sendMessage(userId + "님 환영합니다. \n" + "메뉴입니다. 알맞은 번호를 입력하시면 이동합니다. \n"
				+ "자세한 방법을 알고 싶으시면 2번 아니면 /help를 치시면 됩니다. \n\n" + "1. 채널 입장 \n"
				+ "2. 도움말 \n" + "3. 접속 종료 \n");

	}

	private void displayMatchMenu() throws IOException {
		sendMessage("1. 방 개설하기      2. 방 접속하기      3. 이전 메뉴로 돌아가기 \n");
	}

	// 채널에 접속한 클라이언트에게 보여주는 메뉴이다.
	public void channelMenu() throws IOException {
		// TODO 채널 접속 시 매치 생성이나 여러가지 튜토리얼을 안내한다.
	}

	// 정원이 꽉 찼는지 확인하자.(비교 객체, 비교할 객체 멤버명);
	public boolean isFixedNumber(Object obj) {
		if (obj instanceof GKJoChannel) {
			if (((GKJoChannel) obj).getClientNum() == LIMIT_SERVER)
				return true;

		} else if (obj instanceof GKJoMatch) {
			if (((GKJoMatch) obj).getMatchClientNum() == LIMIT_MATCH)
				return true;
		}
		return false;
	}

	@Override
	public void run() {

		/*
		 * 소켓을 가져왔다. 그러면 그 소켓에 연결된 클라이언트와 메시지를 주고 받아야한다. 클라이언트와 주고 메시지를 주고 받을
		 * reader와 writer 객체를 생성해주자.
		 */

		try {
			this.writer = new BufferedWriter(new OutputStreamWriter(
					socket.getOutputStream()));
			this.reader = new BufferedReader(new InputStreamReader(
					socket.getInputStream()));

			/*
			 * 접속이 완료 되었다. 안내 메시지를 클라이언트에게 전송하자. 전송은 매번 해야할 작업이니까 메서드(기능)를 통해
			 * 재사용하면 좋다.
			 */
			// 안내 메시지와 접속할 ID를 체크한다.
			String newId = null;
			sendMessage("\nGKJo Football Worldcup에 오신 여러분을 환영합니다.\n");
			while (true) {
				sendMessage("아이디를 입력하세요.");
				newId = readMessage();
				/*
				 * 아이디를 입력하라는 메시지를 보냈다. 이젠 클라이언트로부터 메시지를 받자.
				 */

				/*
				 * ID를 받아왔으면 중복된 아이디가 있어야하는지 체크해야할 필요가 있다. 그러려면 접속된 클라이언트의 목록에서
				 * 아이디를 찾아야한다. 아이디를 찾게되면 True, 없으면 False를 반환하는 메소드를 만들면 편리할 것
				 * 같다. 그것을 searchUserId 라고 하겠다.
				 */
				if (searchUserId(newId))
					sendMessage("이미 존재하는 아이디입니다. 다시 입력하세요!");
				else
					break;

			}

			// ID가 없으면 False를 반환하니까 조건에서 나오게 된다.
			this.userId = newId;
			boolean willLeave = false;
			while (!willLeave) {
				// 올바로 접속 되었으니 환영하는 메시지를 보내주자.
				welcomeMsg();

				/*
				 * 환영하는 메시지를 보냈다면 이제 클라이언트가 입력을 받아서 그에 맞는 메뉴로 이동하자
				 */

				/*
				 * 자 여기서 menu는 숫자형식인데 우리가 받아오는 글자는 String 형태이다. 이럴때는
				 * Integer.parseInt로 변환해서 적용하면된다. 그리고 만약 숫자로 변환할 수 없는 문자가 들어온다면
				 * 당연히 Exception이 발생할 것이다. 그럴때는 문제 있다고 알리고 다시 숫자를 받을 수 있도록 하자.
				 */
				int menu = -1;
				/*
				 * 만약 menu를 입력 안했다고 치자 -1이 들어오면 while문은 menu > 0 조건을 만족하지 못하고 다음
				 * 조건은 만족하나 and 조건이므로 false를 반환한다. 거기에 !(not)이 붙어있으므로 true가 된다.
				 * 하지만 1~4 사이의 값이 들어간다면 true를 반환하고 !(not)인 false가 반환되서 while문을
				 * 빠져나오게 된다.
				 */

				// TODO 번호 뿐만 아니라 /로 시작되는건 command 이기 때문에 처리해줘야함
				while (!(menu > MENU_MIN && menu < MENU_MAX)) {
					try {
						sendMessage("메뉴를 입력해주세요!");
						menu = Integer.parseInt(readMessage());

					} catch (NumberFormatException e) {
						// TODO Auto-generated catch block
						sendMessage("정확한 메뉴만 입력해주세요!");
					}
				}

				/*
				 * 올바른 메뉴 입력까지 모든 작업을 끝냈다면 이제는 Switch 문을 이용해서 메뉴별로의 작업을 설정해주자.
				 */

				switch (menu) {
				case 1:
					GKJoChannel selectChannel = null;
					boolean channelExit = false;
					while (!channelExit) {
						while (true) {
							// 채널 접속
							sendMessage("[채널 목록]\n"
									+ GKJoServer.getChannelList());

							/*
							 * 이제 채널이라는 또 하나의 공간을 만들어줘야하기 때문에 클래스를 통해서 만들어주자.
							 */

							/*
							 * 채널 목록이 다 떠있다면 채널을 선택 해야한다.
							 */

							/*
							 * select 된 채널을 알기 위해서 selectChannel 값을 넣어주고 만약 채널에
							 * 대한 올바른 값을 넣지 않으면 반복문은 계속 돈다.
							 */
							sendMessage("채널을 선택해주세요!!");
							int channelNo = 0;

							try {
								channelNo = Integer.parseInt(readMessage());

								// 만약 채널 범위 안에 있는 숫자를 넣지 않는다면
								if (channelNo >= 1
										&& channelNo <= GKJoChannel.channelSeq)
									selectChannel = GKJoServer.channelList
											.get(channelNo - 1);

								if (isFixedNumber(selectChannel)) {
									sendMessage("채널의 인원이 꽉 찼습니다!");
								} else {
									synchronized (selectChannel) {
										selectChannel.setChannelClients(this);
									}

									sendMessage(selectChannel.getChannelTitle()
											+ " 채널로 접속되었습니다.");
									channelExit = true;
									break;
								}
							} catch (NumberFormatException e) {

							} catch (NullPointerException e) {

							} finally {
								sendMessage("올바른 채널을 입력해주세요!");
							}
						}
						/*
						 * 채널에 접속했으니 매치 목록을 보여주자. 매치가 없으면 없다고 나오고 생성에 대한 메뉴를
						 * 띄워주자.
						 */
						boolean matchExit = false;

						while (!matchExit) {
							// 매치 목록
							sendMessage(GKJoServer.getMatchList(selectChannel));
							displayMatchMenu();
							int matchMenu = 0;
							try {
								matchMenu = Integer.parseInt(readMessage());
							} catch (NumberFormatException e) {
								sendMessage("메뉴를 정확하게 입력해주세요!");
							}

							if (matchMenu == 1) { // 방 생성하기

								String matchTitle = null;

								do {
									sendMessage("매치 제목을 정해주세요! (4글자 이상으로)");
								} while ((matchTitle = readMessage()).length() < 4);

								GKJoMatch newSpace = new GKJoMatch(
										selectChannel.getMatchList().size() + 1,
										matchTitle, userId);
								selectChannel.setAddMatchList(newSpace);
								sendMessage("매치가 생성되었습니다.");

								// [1] 중복되는 소스가 있어서 묶어 주는 것이 좋을 것 같다.
								if (newSpace.letClientJoin(this)) {
									selectChannel.setRemoveMatchList(newSpace);
									// 삭제 되었으니 매치번호를 다시 순차적으로 부여한다.
									selectChannel.setMatchNumber();
								}
							} else if (matchMenu == 2) { // 방 접속하기
								String matchSelect = null;
								// 존재하는 방을 선택할때까지 입력하게 한다. 뒤로는 /back
								if (selectChannel.getMatchList().size() == 0) {

								} else {
									boolean chatExit = false;
									while (!chatExit) {
										sendMessage("접속하실 매치 번호를 입력해주세요!(이전 메뉴 /b, 매치목록은 /m)");
										matchSelect = readMessage();
										if ("/b".equals(matchSelect)) {
											break;
										} else if ("/m".equals(matchSelect)) {
											sendMessage(GKJoServer
													.getMatchList(selectChannel));
										} else {
											// 매치번호는 매번 순차적으로 생긴다.
											GKJoMatch m;
											try {
												m = selectChannel
														.getMatchList()
														.get(Integer
																.parseInt(matchSelect) - 1);
												if (isFixedNumber(m)) {
													sendMessage("매치의 정원이 꽉 찼습니다!");
												} else {
													if (m.letClientJoin(this)) {
														selectChannel.setRemoveMatchList(m);
														// 삭제 되었으니 매치번호를 다시 순차적으로 부여한다.
														selectChannel.setMatchNumber();
														chatExit = true;
													}
												}
											} catch (IndexOutOfBoundsException e) {
												// TODO Auto-generated catch
												// block
												sendMessage("올바른 매치번호가 아닙니다!");
											}

										}
									}
								}
							} else if (matchMenu == 3) { // 이전 메뉴로 돌아가는 화면
								/*
								 * 이전 메뉴로 돌아가면 해당 채널에는 존재하지 않기 때문에 접속내용을
								 * 삭제해줘야한다.
								 */
								synchronized (selectChannel) {
									selectChannel.setRemoveClient(this);
								}
								channelExit = false;
								matchExit = true;
							}
						}
					}
					break;
				case 2:
					// 도움말

					break;
				case 3:
					// 종료
					willLeave = true;
					break;
				default:
					break;
				}

			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				sendMessage("/exit");
				socket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				// e.printStackTrace();
			}
		}
	}
}
